﻿using Google.ProtocolBuffers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PolicyServer
{

    // State object for reading client data asynchronously
    public class StateObject
    {
        public const int BufferSize = 1024;
        private byte[] _buffer = new byte[BufferSize];

        public byte[] Buffer
        {
            get { return _buffer; }
            set { _buffer = value; }
        }

        public Socket WorkSocket { get; set; }
            
        private MemoryStream _stream;

        public MemoryStream Stream
        {
            get 
            {
                if (_stream == null)
                {
                    _stream = new MemoryStream();
                }
                return _stream;
            }
        }
        
    }

    public class Protocol
    {
        public ushort CommandID { get; set; }
        public int ReceiveNumber { get; set; }
        public int ReceiveDataLength { get; set; }
        public byte[] RecevieData { get; set; }

        public int SendNumber { get; set; }
        public byte[] SendData { get; set; }
        public byte[] Response { get; set; }
    }

    public class ProtocolDecoder
    {
        public Protocol Decode(Stream stream)
        {
            return null;
        }
    }

    public class ProtocolEncoder
    {
        public Protocol Encode(Protocol protocol)
        {
            return null;
        }
    }

    public class AsynchronousSocketListener
    {
        public static ManualResetEvent allDone = new ManualResetEvent(false);
        public static void StartListening()
        {
            // Data buffer for incoming data.
            byte[] bytes = new Byte[1024];

            // Create a TCP/IP socket.
            Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                listener.Bind(new IPEndPoint(IPAddress.Any, 4530));
                listener.Listen(100);

                while (true)
                {
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    listener.BeginAccept(
                        new AsyncCallback(AcceptCallback),
                        listener);

                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            Console.Read();

        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            allDone.Set();

            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);

            StateObject state = new StateObject();
            state.WorkSocket = handler;
            handler.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
        }

        public static void ReadCallback(IAsyncResult ar)
        {
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.WorkSocket;

            // Read data from the client socket. 
            int bytesRead = handler.EndReceive(ar);
            if (bytesRead == 0)
            {
                HandleRequest(state);
            }
            else
            {
                state.Stream.Write(state.Buffer, 0, bytesRead);
                if (bytesRead < StateObject.BufferSize)
                {
                    HandleRequest(state);
                }
                else
                {
                    handler.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
                }
            }
        }

        private static void HandleRequest(StateObject state)
        {
            ProtocolDecoder decoder = new ProtocolDecoder();
            Protocol protocol = decoder.Decode(state.Stream);

            protocol.SendData = null;
            ProtocolEncoder encoder = new ProtocolEncoder();
            encoder.Encode(protocol);

            state.WorkSocket.BeginSend(protocol.Response, 0, protocol.Response.Length, 0, new AsyncCallback(SendCallback), state);
        }

        private static void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket handler = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                Console.WriteLine("Sent {0} bytes to client.", bytesSent);

                handler.Shutdown(SocketShutdown.Both);
                handler.Close();

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private const byte Head = 0xAA;
        private const byte Tail = 0xFF;

        private static void Send(StateObject state)
        {
            ushort commandId;
            int sendNumber;
            int receiveNumber;
            byte[] data;

            Unpack(state.stream, out commandId, out sendNumber, out receiveNumber, out data);
            Debug.WriteLine("Unpack:CommandId:{0},ReceiveNumber:{1}", commandId, receiveNumber);

            byte[] sendData = null;

            state.WorkSocket.BeginSend(sendData, 0, sendData.Length, 0, new AsyncCallback(SendCallback), state);
        }


        #region Pack

        private static void Pack(ushort commandId, int sendNumber, int receiveNumber, byte[] data)
        {
            var stream = new MemoryStream();

            stream.WriteByte(Head);
            byte[] header = new byte[15];
            header[0] = (byte)(commandId >> 8);
            header[1] = (byte)(commandId >> 0);
            header[2] = (byte)(data.Length >> 24);
            header[3] = (byte)(data.Length >> 16);
            header[4] = (byte)(data.Length >> 8);
            header[5] = (byte)(data.Length >> 0);
            header[6] = 0;
            header[7] = (byte)(sendNumber >> 24);
            header[8] = (byte)(sendNumber >> 16);
            header[9] = (byte)(sendNumber >> 8);
            header[10] = (byte)(sendNumber >> 0);
            header[11] = (byte)(receiveNumber >> 24);
            header[12] = (byte)(receiveNumber >> 16);
            header[13] = (byte)(receiveNumber >> 8);
            header[14] = (byte)(receiveNumber >> 0);

            for (int i = 0; i < header.Length; i++)
            {
                this.Write(stream, header[i]);
            }

            stream.Write(header, 0, header.Length);
            stream.Write(data, 0, data.Length);
            stream.WriteByte(Tail);

            if (_sendQueue == null)
            {
                this.TcpChannel.Send(stream.ToArray());
            }
            else
            {
                _sendQueue.Enqueue(stream.ToArray());
            }
        }

        private void Write(MemoryStream stream, byte value)
        {
            switch (value)
            {
                case Head:
                    stream.WriteByte(HeadEscape);
                    stream.WriteByte(HeadEscape1);
                    break;
                case HeadEscape:
                    stream.WriteByte(HeadEscape);
                    stream.WriteByte(HeadEscape2);
                    break;
                case Tail:
                    stream.WriteByte(TailEscape);
                    stream.WriteByte(TailEscape1);
                    break;
                case TailEscape:
                    stream.WriteByte(TailEscape);
                    stream.WriteByte(TailEscape2);
                    break;
                default:
                    stream.WriteByte(value);
                    break;
            }
        }
        #endregion

        #region Unpack
        private int Unpack(MemoryStream stream)
        {
            ushort commandId;
            int sendNumber;
            int receiveNumber;
            byte[] data;

            this.Unpack(stream, out commandId, out sendNumber, out receiveNumber, out data);
            Debug.WriteLine("Unpack:CommandId:{0},ReceiveNumber:{1}", commandId, receiveNumber);

            //var message = this.GetMessage((CommandNumber)commandId, data);
            IMessage message = null;

            TaskCompletionSource<IMessage> tcs;
            if (_callDictionary.TryGetValue(receiveNumber, out tcs))
            {
                _callDictionary.Remove(receiveNumber);

                tcs.SetResult(message);
            }
            else
            {
                //this.OnReceive(receiveNumber, (CommandNumber)commandId, message);
            }

            return sendNumber;
        }

        private static bool Unpack(MemoryStream stream, out ushort commandId, out int sendNumber, out int receiveNumber, out byte[] data)
        {
            byte header0 = (byte)stream.ReadByte();
            byte header1 = (byte)stream.ReadByte();
            commandId = (ushort)((header0 << 8) + header1);

            byte header2 = (byte)stream.ReadByte();
            byte header3 = (byte)stream.ReadByte();
            byte header4 = (byte)stream.ReadByte();
            byte header5 = (byte)stream.ReadByte();
            int length = (header2 << 24) + (header3 << 16) + (header4 << 8) + (header5 << 0);

            byte header6 = (byte)stream.ReadByte();

            byte header7 = (byte)stream.ReadByte();
            byte header8 = (byte)stream.ReadByte();
            byte header9 = (byte)stream.ReadByte();
            byte header10 = (byte)stream.ReadByte();
            sendNumber = (header7 << 24) + (header8 << 16) + (header9 << 8) + header10;

            byte header11 = (byte)stream.ReadByte();
            byte header12 = (byte)stream.ReadByte();
            byte header13 = (byte)stream.ReadByte();
            byte header14 = (byte)stream.ReadByte();
            receiveNumber = (header11 << 24) + (header12 << 16) + (header13 << 8) + header14;

            List<byte> list = new List<byte>();
            while (true)
            {
                byte value = (byte)stream.ReadByte();

                if (stream.Position == stream.Length)
                {
                    data = list.ToArray();
                    return list.Count == length;
                }

                list.Add(value);
            }
        }
        #endregion
    }
}
